package com.rabtman.common.utils;

import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.support.v4.util.SimpleArrayMap;
import com.rabtman.common.BuildConfig;
import java.util.Collections;
import java.util.Map;
import java.util.Set;


public final class SPUtils {

  private static SimpleArrayMap<String, SPUtils> SP_UTILS_MAP = new SimpleArrayMap<>();
  private SharedPreferences sp;

  private SPUtils(final String spName) {
    sp = Utils.getContext().getSharedPreferences(spName, Context.MODE_PRIVATE);
  }

  /**
   * Return the single {@link SPUtils} instance
   *
   * @return the single {@link SPUtils} instance
   */
  public static SPUtils getInstance() {
    return getInstance(BuildConfig.applicationId);
  }

  /**
   * Return the single {@link SPUtils} instance
   *
   * @param spName The name of sp.
   * @return the single {@link SPUtils} instance
   */
  public static SPUtils getInstance(String spName) {
    SPUtils spUtils = SP_UTILS_MAP.get(spName);
    if (spUtils == null) {
      spUtils = new SPUtils(spName);
      SP_UTILS_MAP.put(spName, spUtils);
    }
    return spUtils;
  }

  private static boolean isSpace(final String s) {
    if (s == null) {
      return true;
    }
    for (int i = 0, len = s.length(); i < len; ++i) {
      if (!Character.isWhitespace(s.charAt(i))) {
        return false;
      }
    }
    return true;
  }

  /**
   * Put the string value in sp.
   *
   * @param key The key of sp.
   * @param value The value of sp.
   */
  public void put(@NonNull final String key, final String value) {
    put(key, value, false);
  }

  /**
   * Put the string value in sp.
   *
   * @param key The key of sp.
   * @param value The value of sp.
   * @param isCommit True to use {@link SharedPreferences.Editor#commit()}, false to use {@link
   * SharedPreferences.Editor#apply()}
   */
  public void put(@NonNull final String key, final String value, final boolean isCommit) {
    if (isCommit) {
      sp.edit().putString(key, value).commit();
    } else {
      sp.edit().putString(key, value).apply();
    }
  }

  /**
   * Return the string value in sp.
   *
   * @param key The key of sp.
   * @return the string value if sp exists or {@code ""} otherwise
   */
  public String getString(@NonNull final String key) {
    return getString(key, "");
  }

  /**
   * Return the string value in sp.
   *
   * @param key The key of sp.
   * @param defaultValue The default value if the sp doesn't exist.
   * @return the string value if sp exists or {@code defaultValue} otherwise
   */
  public String getString(@NonNull final String key, final String defaultValue) {
    return sp.getString(key, defaultValue);
  }

  /**
   * Put the int value in sp.
   *
   * @param key The key of sp.
   * @param value The value of sp.
   */
  public void put(@NonNull final String key, final int value) {
    put(key, value, false);
  }

  /**
   * Put the int value in sp.
   *
   * @param key The key of sp.
   * @param value The value of sp.
   * @param isCommit True to use {@link SharedPreferences.Editor#commit()}, false to use {@link
   * SharedPreferences.Editor#apply()}
   */
  public void put(@NonNull final String key, final int value, final boolean isCommit) {
    if (isCommit) {
      sp.edit().putInt(key, value).commit();
    } else {
      sp.edit().putInt(key, value).apply();
    }
  }

  /**
   * Return the int value in sp.
   *
   * @param key The key of sp.
   * @return the int value if sp exists or {@code -1} otherwise
   */
  public int getInt(@NonNull final String key) {
    return getInt(key, -1);
  }

  /**
   * Return the int value in sp.
   *
   * @param key The key of sp.
   * @param defaultValue The default value if the sp doesn't exist.
   * @return the int value if sp exists or {@code defaultValue} otherwise
   */
  public int getInt(@NonNull final String key, final int defaultValue) {
    return sp.getInt(key, defaultValue);
  }

  /**
   * Put the long value in sp.
   *
   * @param key The key of sp.
   * @param value The value of sp.
   */
  public void put(@NonNull final String key, final long value) {
    put(key, value, false);
  }

  /**
   * Put the long value in sp.
   *
   * @param key The key of sp.
   * @param value The value of sp.
   * @param isCommit True to use {@link SharedPreferences.Editor#commit()}, false to use {@link
   * SharedPreferences.Editor#apply()}
   */
  public void put(@NonNull final String key, final long value, final boolean isCommit) {
    if (isCommit) {
      sp.edit().putLong(key, value).commit();
    } else {
      sp.edit().putLong(key, value).apply();
    }
  }

  /**
   * Return the long value in sp.
   *
   * @param key The key of sp.
   * @return the long value if sp exists or {@code -1} otherwise
   */
  public long getLong(@NonNull final String key) {
    return getLong(key, -1L);
  }

  /**
   * Return the long value in sp.
   *
   * @param key The key of sp.
   * @param defaultValue The default value if the sp doesn't exist.
   * @return the long value if sp exists or {@code defaultValue} otherwise
   */
  public long getLong(@NonNull final String key, final long defaultValue) {
    return sp.getLong(key, defaultValue);
  }

  /**
   * Put the float value in sp.
   *
   * @param key The key of sp.
   * @param value The value of sp.
   */
  public void put(@NonNull final String key, final float value) {
    put(key, value, false);
  }

  /**
   * Put the float value in sp.
   *
   * @param key The key of sp.
   * @param value The value of sp.
   * @param isCommit True to use {@link SharedPreferences.Editor#commit()}, false to use {@link
   * SharedPreferences.Editor#apply()}
   */
  public void put(@NonNull final String key, final float value, final boolean isCommit) {
    if (isCommit) {
      sp.edit().putFloat(key, value).commit();
    } else {
      sp.edit().putFloat(key, value).apply();
    }
  }

  /**
   * Return the float value in sp.
   *
   * @param key The key of sp.
   * @return the float value if sp exists or {@code -1f} otherwise
   */
  public float getFloat(@NonNull final String key) {
    return getFloat(key, -1f);
  }

  /**
   * Return the float value in sp.
   *
   * @param key The key of sp.
   * @param defaultValue The default value if the sp doesn't exist.
   * @return the float value if sp exists or {@code defaultValue} otherwise
   */
  public float getFloat(@NonNull final String key, final float defaultValue) {
    return sp.getFloat(key, defaultValue);
  }

  /**
   * Put the boolean value in sp.
   *
   * @param key The key of sp.
   * @param value The value of sp.
   */
  public void put(@NonNull final String key, final boolean value) {
    put(key, value, false);
  }

  /**
   * Put the boolean value in sp.
   *
   * @param key The key of sp.
   * @param value The value of sp.
   * @param isCommit True to use {@link SharedPreferences.Editor#commit()}, false to use {@link
   * SharedPreferences.Editor#apply()}
   */
  public void put(@NonNull final String key, final boolean value, final boolean isCommit) {
    if (isCommit) {
      sp.edit().putBoolean(key, value).commit();
    } else {
      sp.edit().putBoolean(key, value).apply();
    }
  }

  /**
   * Return the boolean value in sp.
   *
   * @param key The key of sp.
   * @return the boolean value if sp exists or {@code false} otherwise
   */
  public boolean getBoolean(@NonNull final String key) {
    return getBoolean(key, false);
  }

  /**
   * Return the boolean value in sp.
   *
   * @param key The key of sp.
   * @param defaultValue The default value if the sp doesn't exist.
   * @return the boolean value if sp exists or {@code defaultValue} otherwise
   */
  public boolean getBoolean(@NonNull final String key, final boolean defaultValue) {
    return sp.getBoolean(key, defaultValue);
  }

  /**
   * Put the set of string value in sp.
   *
   * @param key The key of sp.
   * @param value The value of sp.
   */
  public void put(@NonNull final String key, final Set<String> value) {
    put(key, value, false);
  }

  /**
   * Put the set of string value in sp.
   *
   * @param key The key of sp.
   * @param value The value of sp.
   * @param isCommit True to use {@link SharedPreferences.Editor#commit()}, false to use {@link
   * SharedPreferences.Editor#apply()}
   */
  public void put(@NonNull final String key,
      final Set<String> value,
      final boolean isCommit) {
    if (isCommit) {
      sp.edit().putStringSet(key, value).commit();
    } else {
      sp.edit().putStringSet(key, value).apply();
    }
  }

  /**
   * Return the set of string value in sp.
   *
   * @param key The key of sp.
   * @return the set of string value if sp exists or {@code Collections.<String>emptySet()}
   * otherwise
   */
  public Set<String> getStringSet(@NonNull final String key) {
    return getStringSet(key, Collections.<String>emptySet());
  }

  /**
   * Return the set of string value in sp.
   *
   * @param key The key of sp.
   * @param defaultValue The default value if the sp doesn't exist.
   * @return the set of string value if sp exists or {@code defaultValue} otherwise
   */
  public Set<String> getStringSet(@NonNull final String key,
      final Set<String> defaultValue) {
    return sp.getStringSet(key, defaultValue);
  }

  /**
   * Return all values in sp.
   *
   * @return all values in sp
   */
  public Map<String, ?> getAll() {
    return sp.getAll();
  }

  /**
   * Return whether the sp contains the preference.
   *
   * @param key The key of sp.
   * @return {@code true}: yes<br>{@code false}: no
   */
  public boolean contains(@NonNull final String key) {
    return sp.contains(key);
  }

  /**
   * Remove the preference in sp.
   *
   * @param key The key of sp.
   */
  public void remove(@NonNull final String key) {
    remove(key, false);
  }

  /**
   * Remove the preference in sp.
   *
   * @param key The key of sp.
   * @param isCommit True to use {@link SharedPreferences.Editor#commit()}, false to use {@link
   * SharedPreferences.Editor#apply()}
   */
  public void remove(@NonNull final String key, final boolean isCommit) {
    if (isCommit) {
      sp.edit().remove(key).commit();
    } else {
      sp.edit().remove(key).apply();
    }
  }

  /**
   * Remove all preferences in sp.
   */
  public void clear() {
    clear(false);
  }

  /**
   * Remove all preferences in sp.
   *
   * @param isCommit True to use {@link SharedPreferences.Editor#commit()}, false to use {@link
   * SharedPreferences.Editor#apply()}
   */
  public void clear(final boolean isCommit) {
    if (isCommit) {
      sp.edit().clear().commit();
    } else {
      sp.edit().clear().apply();
    }
  }

}